<?php

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Exception;
use swoole_server;


/**
 * 获取历史火币数据，并进行保存
 * Class HistoryTrade
 * @package app\command
 */
class HttpServer extends Command
{


    /**
     * 进程参数设置
     */
    const PROCESS_CONFIG = [
        'SERVER'    => [
            'REACTOR_NUM'   => 4,   // 最大线程数
            'WORKER_NUM'    => 20,  // 工作进程数
            'MAX_REQUEST'   => 1,    // 最大请求数
            'TASK_WORKER_NUM'    => 16,  // tash 工作进程数【后台执行】
            'DEFAULT_PORT'  => 13902,   // 默认端口
            'DAEMONIZE'  => 0,   // 是否开启守护
        ],
        'REQUEST'   => [    //请求参数设置
            'CURL_TIMEOUT'  => 3,   // curl 请求超时时间
        ],
        'LOG'   => [    // 日志处理
            'ERROR' => [    // 错误日志
                'TYPE_NAME'  => 'HTTP_ERROR', // 日志类里对应的类型
                'TYPE_CUSTOM_LIST'  => [
                    'INIT'  => 'init',   //初始化错误
                    'EXTEND'    => 'extend',    //扩展
                ],  // 自定义类型
            ],
            'RECORD'    => [    //单纯的记录日志
                'TYPE_NAME'  => 'HTTP_RECORD', // 日志类里对应的类型
                'TYPE_CUSTOM_LIST'  => [
                    'EXTEND'    => 'extend',    //扩展
                ],  // 自定义类型
            ],
        ],
    ];

    /**
     * ENV 配置参数KEY列表
     */
    const ENV_KEY_LIST = [
        'SERVER_HTTP_LOCALHOST_IP'  => 'http_server.localhost_ip',  // 判断本机IP地址
        'SERVER_HTTP_MASTER'   => 'http_server.is_master', // http 判断本机是否启用服务器
        'SERVER_HTTP_PORT'   => 'http_server.port', // http 服务器端口
        'SERVER_HTTP_CMD_IP'  => 'http_server.cmd_request_ip',  // CMD 对应的默认地址
        'SERVER_HTTP_CMD_IP_LIMIT'  => 'http_server.cmd_ip_limit',    //IP 限制权限
        'CLIENT_HTTP_REQUEST_TIMEOUT'  => 'http_server.client_timeout',    // 客户端请求超时时间
    ];

    /**
     * cmd 列表
     */
    const CMD_LIST = [
        'REFRESH'   => 'Refresh',    //刷新处理
        'GITPULL'   => 'gitPUll',   // 拉取代码
        'WEBSWOOLE'   => 'webSwoole',   // 请求页面信息
        'VALIDATE'   => 'validate',   // 验证执行页面
    ];

    // cmd IP限制
    private $cmdIpLimit = [];

    /**
     * 扩展列表
     * @var array
     */
    private $extendList = [];



    /**
     * 重写configure
     * {@inheritdoc}
     */
    protected function configure()
    {
        // 命令的名字（"think" 后面的部分）
        $this->setName('HttpServer')
            // 运行 "php think list" 时的简短描述
            ->setDescription('开启http服务器，监控请求处理')
            // 运行命令时使用 "--help" 选项时的完整命令描述
            ->setHelp("一直执行 无参数");
    }

    /**
     *  * 重写execute
     *  * {@inheritdoc}
     *
     * @param Input $input
     * @param Output $output
     */
    public function execute(Input $input, Output $output)
    {
        // 初始化
        $this->init();

        // 开启交互监听
        $this->createServer();
    }

    /**
     * 请求前的初始化
     */
    public function init() {
        // 导入插件处理
        $this->importExtend();

        //  获取 cmd 中的 IP 限制
        $this->getLimit();
    }

    /**
     * 导入插件
     */
    private function importExtend() {
        $extendLevels = ['extend','http'];
        $classDir = implode(DIRECTORY_SEPARATOR,array_merge([dirname(__FILE__)],$extendLevels));
        if(!is_dir($classDir)) {
            return null;
        }
        /********* 获取扩展空间名 *********/
        $namespaceFunc = function($extendLevels){
            $namespace = explode('\\',self::class);
            array_pop($namespace);
            $namespace = array_merge($namespace,$extendLevels);
            array_unshift($namespace,'');
            $namespace[] = '';
            $namespace = implode('\\',$namespace);
            return $namespace;
        };
        /******* END 获取扩展空间名 *******/
        $namespace = $namespaceFunc($extendLevels);

        // 获取目录资源
        $resource = opendir($classDir);
        while ($filename = readdir($resource)) {
            if(!preg_match('/([a-zA-Z]+)\.php/',$filename,$match) || strtoupper($filename) == 'BASE.PHP') {
                continue;
            }
            try {
                $extendName = $match[1];
                $className = $namespace . $extendName;
                $className = new $className();
                $this->extendList[strtoupper($extendName)] = $className;
            }catch (\Exception $e) {
                // 记录sql错误信息
                self::log($e,'INIT','ERROR');
            }

        }
        return true;
    }



    /**
     * 获取 cmd 中的 IP 限制
     */
    private function getLimit() {
        // 获取IP限制
        $limit = trim(env(self::ENV_KEY_LIST['SERVER_HTTP_CMD_IP_LIMIT']));
        $limit = explode(' ',$limit);
        array_map(function($limit){
            $limit = explode(':',$limit);
            if(count($limit) != 2) {
                return '';
            }
            $this->cmdIpLimit[strtoupper($limit[0])] = array_map(function($ip){return trim($ip);},explode(',',$limit[1]));
            return '';
        },$limit);
        return true;
    }

    /**
     * 通过cmd 获取请求ip
     * @param $cmd cmd
     * @param $forceCurl 指定IP
     * @return false|string[] [地址,端口]
     */
    static function cmdToAddr($cmd,$forceCurl) {
        $cmd = strtoupper($cmd);
        $host = '';
        if(is_string($forceCurl) && $forceCurl) {
            $host = $forceCurl;
        } else {
            $cmdList = explode(' ',env(self::ENV_KEY_LIST['SERVER_HTTP_CMD_IP'],''));
            foreach($cmdList as $cmdDetails) {
                $cmdDetails = explode(',',$cmdDetails);
                if(strtoupper(trim($cmdDetails[0])) == $cmd) {
                    $host = trim($cmdDetails[1]);
                    continue;
                }
            }
            if(!$host) {
                $host = '127.0.0.1';
            }
        }
        $host = explode(':',$host);
        if(!array_key_exists(1,$host)) {
            $host[1] = self::serverPort();
        }
        if($host[0] == env(self::ENV_KEY_LIST['SERVER_HTTP_LOCALHOST_IP'],'')) {
            $host[0] = '127.0.0.1';
        }
        return $host;
    }

    /**
     * 发送消息
     * @param $message 消息内容，随意值，会直接json处理
     * @param int|null $portIndex
     * @param bool $forceCurl 是否强制使用 curl【党委指定IP时：指定IP加端口则为指定地址请求】
     * @return array|bool|false|string|null
     */
    static function request(string $cmd,array $params = null,$forceCurl = false) {
        $result = null;
        $params['cmd'] = $cmd;
        list($host,$port) = self::cmdToAddr($cmd,$forceCurl);

        try {
            // 判断是否为本地请求
            if(!$forceCurl && $host == '127.0.0.1' && $port == self::serverPort()) {
                $request = new \swoole_http_request();
                $request->server = [
                    'remote_addr'    => '127.0.0.1',
                ];
                $request->get = $params;
                $classname = self::class;
                $classname = new $classname();
                $classname->init();
                $result = $classname->execReuqest($request);
                if(is_array($result)) {
                    $result = json_encode($result);
                }
            } else {

                // 整理请求地址
                $url = 'http://' . $host . ':' . $port . '?' . http_build_query($params);

                /******* 获取请求超时时间 ********/
                $timeOut = env(self::ENV_KEY_LIST['CLIENT_HTTP_REQUEST_TIMEOUT']);
                $timeOut = empty($timeOut)?self::PROCESS_CONFIG['REQUEST']['CURL_TIMEOUT']:$timeOut;
                /***** END 获取请求超时时间 ******/

                $curl = curl_init();
                //设置抓取的url
                curl_setopt($curl, CURLOPT_URL, $url);
                //设置头文件的信息作为数据流输出
                curl_setopt($curl, CURLOPT_HEADER, 0);
                // 超时设置,以秒为单位
                curl_setopt($curl, CURLOPT_TIMEOUT,$timeOut);

                //设置获取的信息以文件流的形式返回，而不是直接输出。
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
                //执行命令
                $result = curl_exec($curl);

                // 显示错误信息
                if (curl_error($curl)) {
                    throw new Exception( "Error: " . curl_error($curl));
                } else {
                    curl_close($curl);
                }
            }
        } catch (\Exception $e) {
            $result = null; // 请求错误，返回空
        }

        return $result;
    }



    /**
     * 启动服务器
     */
    private function createServer() {
        // 创建udp服务器
        $server = new \swoole_http_server('0.0.0.0',self::serverPort());
        $server->set([
            'reactor_num'   => self::PROCESS_CONFIG['SERVER']['REACTOR_NUM'],     // reactor thread num
            'worker_num'    => self::PROCESS_CONFIG['SERVER']['WORKER_NUM'],     // worker process num
            'task_worker_num'    => self::PROCESS_CONFIG['SERVER']['TASK_WORKER_NUM'],     // task worker process num
            'max_request'   => self::PROCESS_CONFIG['SERVER']['MAX_REQUEST'],
            'daemonize'   => self::PROCESS_CONFIG['SERVER']['DAEMONIZE'],   // 判断是否开启守护
        ]);

        //设置request事件回调
        //函数有两个参数：
        //swoole_http_request对象，负责http请求相关信息
        //swoole_http_response对象，负责向客户端响应相关信息
        $server->on('request', function (\swoole_http_request $request, \swoole_http_response $response) use($server){
            ob_start(); // 开始php缓存，写在最前面
            $response->header("Content-Type", "text/html; charset=utf-8");
            $sendMessage = '';
            try{
                $sendMessage = $this->execReuqest($request);
                if(is_array($sendMessage)) {
                    if(array_key_exists('data',$sendMessage) && is_array($sendMessage['data']) && array_key_exists('Task',$sendMessage['data'])) {
                        $server->task($sendMessage['data']['Task']);
                        unset($sendMessage['data']['Task']);
                    }
                    $sendMessage = json_encode($sendMessage);
                }
                $sendMessage = ob_get_contents() . $sendMessage;
                if(json_decode($sendMessage)) {
                    $response->header("Content-Type", "application/json; charset=utf-8");
                }

            }catch (\Exception $e) {
                $sendMessage = '<pre>' . print_r($e,true);
            }
            ob_clean();
            //向客户端发送信息
            $response->end($sendMessage);
        });

        // 执行后台运行处理
        $server->on('Task',function($serv, $task_id, $from_id, $data){
            // 获取请求参数
            $params = array_key_exists(2,$data)?[$data[2]]:[];
            // 请求后台执行处理
            call_user_func_array([$data[0],$data[1]],$params);
        });

        $server->on('Finish',function($serv,$task_id, $data){

        });
        $server->start();

    }

    /**
     * 执行请求，并进行限制处理
     * @param $data 接收的数据
     */
    public function execReuqest($request) {
        // 获取客户端地址
        $remoteAddr = $request->server['remote_addr'];
        $get = $request->get;
        $get = empty($get)?[]:$get;

        if(!array_key_exists('cmd',$get) || empty($get['cmd'])) {
            return 'api Error';
        }

        if(!method_exists($this,($cmdMethod = 'cmd' . strtoupper($get['cmd'][0]) . substr($get['cmd'],1)))) {
            $extend = $get['cmd'];
            $method = array_key_exists('method',$get)?$get['method']:$extend;
            $method = strtoupper($method[0]) . substr($method,1);
            $extend = strtoupper($extend);
            if(!array_key_exists($extend,$this->extendList)) {  // 扩展不存在
                return 'api extend Error';
            }
            $callFunction = [$this->extendList[$extend],'call'];
            $callParams = [$method,$get,$remoteAddr];
        } else {
            $cmd = strtoupper($get['cmd']);
            if(!in_array($remoteAddr,['127.0.0.1']) && array_key_exists($cmd,$this->cmdIpLimit) && !in_array($remoteAddr,$this->cmdIpLimit[$cmd])) {
                return 'ip limit';
            }
            $callFunction = [$this,$cmdMethod];
            $callParams = [$get,$request];
        }
        $result = [
            'status'    => 'ok',
            'errMsg'    => '',
            'data'      => null,
        ];
        try {
            $result['data'] = call_user_func_array($callFunction,$callParams);
        }catch (\Exception $e) {
            $result['status'] = 'err';
            $result['errMsg'] = $e->getMessage();
        }

        return $result;
    }

    /**
     * 验证系统执行状态
     * @param $get
     * @param null $request
     * @return bool
     */
    private function cmdValidate($get,$request = null) {
        return Crontab::validateWrite($get);
    }



    /*
     * 刷新数据
     */
    private function cmdRefresh($get,$request = null) {
        $type = strtoupper(array_key_exists('type',$get)?$get['type']:'');
        switch($type) {
            case 'USERRULE':    //刷新用户规则
                for($i=0;$i<3;++$i) {
                    try {
                        HistoryTrade::tcpTableQuery('userRuleRefresh',$i);  //除不使用redis之外，其他直接返回操作结果
                    }catch (\Exception $e) {
                    }
                }
                return true;
        }
        $this->throwErr(0);
    }

    /**
     * 拉取代码
     * @param $get
     * @param $post
     */
    private function cmdGitPUll($get,$request) {
        $header = $request->header;
        if(!array_key_exists('user-agent',$header) || $header['user-agent'] != 'git-oschina-hook') {
            $this->throwErr(0);
        }

        // 编写更新脚本
        $path = dirname(dirname(dirname(__FILE__)));
        exec("cd " . $path . "  && git reset --hard && git pull",$data);

        // 输出返回执行结果
        return [
            'time'  => date('Y-m-d H:i:s ',time()),
            'result'    => $data,
        ];
    }

    /**
     * 请求web页面
     * @param $get
     * @param $request
     */
    private function cmdWebSwoole($get,$request) {
        if(!array_key_exists('c',$get)) {
            $this->throwErr(0);
        }
        $swooleClass = (new \app\api\controller\wap\Swoole());
        return call_user_func_array([$swooleClass,$get['c']],[$request]);
    }

    /**
     * 抛出异常
     * @param int $code 错误码
     * @throws Exception
     */
    private function throwErr($code = 0) {
        $errorList = [
            0   => 'params Error',
        ];
        throw new Exception($errorList[$code]);
    }


    /**
     * 获取服务器监听端口
     */
    static function serverPort()
    {
        if(!$port = env(static::ENV_KEY_LIST['SERVER_HTTP_PORT'])) {
            $port = self::PROCESS_CONFIG['SERVER']['DEFAULT_PORT'];
        }
        return $port;
    }


    /**
     * 添加记录日志
     * @param $message 日志信息（错误时直接填写异常）
     * @param string $type 类型，必须在对应的 TYPE_CUSTOM_LIST 中
     * @param string $recordType 记录类型【PROCESS_CONFIG 中 LOG 下面的建】
     * @return bool
     */
    static public function log($message,string $type,$recordType = 'RECORD',$serializeMessage = false) {
        $recordConfig = self::PROCESS_CONFIG['LOG'][$recordType];
        $write = [
            $recordConfig['TYPE_CUSTOM_LIST'][$type],
            is_object($message)?Swoole::getErrorAndFiles($message):$message
        ];
        if($serializeMessage) {
            $write[1] = serialize($write[1]);
        }
        Log::write(implode(' ',$write),Log::LOG_TYPE_LIST[$recordConfig['TYPE_NAME']],false);
        return true;
    }

}